/*
 * CVideoRender.cpp
 *
 *  Created on: 2016年3月18日
 *      Author: terry
 */

#include "CVideoRender.h"

#ifdef WIN32
#include "WinVideoWindow.h"
#if defined(_MSC_VER)
#include "WtlVideoWindow.h"
#else
#endif //
#endif //WIN32

#include "SdlVideoWindow.h"

#include "FfmpegUtil.h"
#include "TimeHelper.h"
#include "CLog.h"

#include "AppProperties.h"
#include "MediaProp.h"
#include "TStringUtil.h"


namespace av
{

static const int MAX_SYNC_TIME = 2 * 1000 * 1000;
static const size_t MAX_QUEUE_SIZE = 80;


CVideoRender::CVideoRender():
		m_videoWindow(),
		m_state(STATE_STOPPED),
		m_refClock(),
        m_nosync(),
		m_scale(),
		m_waitForKeyFrame(),
		m_isLive(),
		m_ratioX(),
		m_ratioY()
{

    std::string renderName = AppProperties::getProperties().getString(MEDIA_VIDEO_PROP_RENDER);
    comn::StringUtil::toLower(renderName);

#ifdef WIN32
	m_videoWindow.reset(new WinVideoWindow());
#else
    //if (renderName == "sdl")
    {
        m_videoWindow.reset(new SdlVideoWindow());
    }
#endif //

	//m_videoWindow.reset(new SdlVideoWindow());
}

CVideoRender::~CVideoRender()
{
	close();
}

bool CVideoRender::getTimePoint(int64_t& clock, int64_t& pts)
{
	if (!m_curPoint.isSet())
	{
		return false;
	}

	clock = m_curPoint.m_clock;
    pts = m_curPoint.m_pts;
    return true;
}

void CVideoRender::close()
{
    if (getState() != STATE_STOPPED)
    {
	    stop();
    }

	flush();

	m_videoWindow->close();

	if (isRunning())
	{
		comn::Thread::stop();
	}

	m_isLive = false;
}

bool CVideoRender::isOpen()
{
	return true;
}

int CVideoRender::play()
{
	setState(STATE_PLAYING);

	m_videoWindow->setState(STATE_PLAYING);

	postCommand(kPlay);

	return 0;
}

int CVideoRender::pause()
{
	setState(STATE_PAUSED);

	m_videoWindow->setState(STATE_PAUSED);

	postCommand(kPause);

	return 0;
}

void CVideoRender::stop()
{
	setState(STATE_STOPPED);

	m_videoWindow->setState(STATE_STOPPED);

    flush();

    postCommand(kStop);
}

int CVideoRender::getState()
{
	return m_state;
}

void CVideoRender::setFormat(const MediaFormat& fmt)
{

}

int CVideoRender::write(AVFramePtr frame)
{
	if (!frame)
	{
		return EINVAL;
	}

    if (m_state == STATE_STOPPED)
    {
        return EAGAIN;
    }

    if (m_isLive && (m_frames.size() > 4))
    {
        //CLog::debug("video frame. pts:%I64d, pkt(%I64d, %I64d) duration:%d, que:%d\n",
        //    frame->pts, frame->pkt_dts/1000, frame->pkt_pts/1000, (int)frame->pkt_duration, m_frames.size());
    }

	m_frames.push(frame);
	m_event.post();

	return 0;
}

void CVideoRender::flush()
{
	m_frames.clear();

	m_originPoint.reset();
    m_curPoint.reset();

    m_videoWindow->flush();
}


void CVideoRender::setScale(AVRational scale)
{
	postCommand(kSetScale, scale.num, scale.den);
}

bool CVideoRender::setProp(const std::string& name, const std::string& value)
{
    if (name == MEDIA_VIDEO_PROP_NOSYNC)
    {
        comn::AutoCritSec lock(m_cs);
        m_nosync = true;
    }
    else if (name == MEDIA_PLAYER_PROP_LIVE_MODE)
    {
        comn::AutoCritSec lock(m_cs);
        comn::StringCast::toValue(value, m_isLive);
    }

    return true;
}

int CVideoRender::open()
{
	comn::Thread::start();

	return 0;
}

void CVideoRender::setVideoWnd(void* hwnd)
{
	if (m_videoWindow->isOpen())
	{
		m_videoWindow->close();
	}

	m_videoWindow->open(hwnd);
}

bool CVideoRender::isFullscreen()
{
	return m_videoWindow->isFullscreen();
}

int CVideoRender::setFullscreen(bool full)
{
	return m_videoWindow->setFullscreen(full);
}

int CVideoRender::setBackImage(const char* filepath)
{
	return m_videoWindow->setBackImage(filepath);
}



int CVideoRender::setRatio(int width, int height)
{
	m_ratioX = width;
	m_ratioY = height;

	m_videoWindow->setRatio(width, height);
	return 0;
}

void CVideoRender::getRatio(int& width, int& height)
{
	width = m_ratioX;
	height = m_ratioY;
}

void CVideoRender::setRefClock(ReferenceClock* refClock)
{
	m_refClock = refClock;
}

void CVideoRender::step()
{
	postCommand(kStep);
}

void CVideoRender::nextKeyFrame()
{
	postCommand(kNextKeyFrame);
}

void CVideoRender::setVideoWindow(VideoWindow* window)
{

}

void CVideoRender::onCommand(Command& cmd)
{
	if (cmd.type == kPlay)
	{
		handlePlay();
	}
	else if (cmd.type == kPause)
	{
		handlePause();
	}
	else if (cmd.type == kStop)
	{
		handleStop();
	}
	else if (cmd.type == kSetScale)
	{
		AVRational scale;
		scale.num = cmd.at(0).toInt();
		scale.den = cmd.at(1).toInt();

		handleSetScale(scale);
	}
	else if (cmd.type == kStep)
	{
		handleStep();
	}
	else if (cmd.type == kNextKeyFrame)
	{
		handleNextKeyFrame();
	}
}

void CVideoRender::onIdle()
{
	int state = getState();
	if (state == STATE_STOPPED)
	{
		m_event.timedwait(-1);
		return;
	}
	else if (state == STATE_PAUSED)
	{
		if (m_waitForKeyFrame)
		{
			AVFramePtr frame = waitAndGet(-1);
			if (frame && frame->key_frame)
			{
				m_waitForKeyFrame = false;
				m_videoWindow->draw(frame.get());
			}
		}
        else
        {
            m_event.timedwait(-1);
        }
		return;
	}

	AVFramePtr frame = waitAndGet(-1);
	if (!frame)
	{
		return;
	}

	if (m_isLive)
	{
		if (m_frames.size() > MAX_QUEUE_SIZE)
		{
			size_t count = m_frames.popUntilSize(2);
			CLog::warning("CVideoRender drop %d frames.\n", count);

			m_originPoint.reset();
			m_curPoint.reset();
			return;
		}
	}

    int delay = 0;
	int64_t pts = getPts(frame);
	if ((pts == AV_NOPTS_VALUE) || (pts < 0))
	{
		delay = frame->pkt_duration;
		m_originPoint.reset();
		m_curPoint.reset();
	}
	else
	{
		if (!m_originPoint.isSet() || (m_originPoint.m_pts == AV_NOPTS_VALUE))
		{
			m_originPoint.reset(getClock(), pts);
		}
		else if (m_curPoint.isSet())
		{
			if ((pts - m_curPoint.m_pts) >= MAX_SYNC_TIME)
			{
				m_originPoint.reset(getClock(), pts);
				m_curPoint.reset();
			}
		}

		delay = computeDelay(frame);
	}

   // if (m_frames.size() > 4)
   // {
	//CLog::debug("video frame. queue:%d, pts:%I64d, delay:%d, duration:%d\n",
	//		m_frames.size(), frame->pts, delay, (int)frame->pkt_duration);
   // }

	if (m_nosync)
	{
		delay = 0;
	}

	delayTime(delay);

	m_curPoint.reset(getClock(), pts);
	m_videoWindow->draw(frame.get());
}

void CVideoRender::doStop()
{
	util::CommandThread::doStop();

	m_event.post();
	m_eventDelay.post();
}

void CVideoRender::setState(StreamState state)
{
	{
		comn::AutoCritSec lock(m_cs);
		m_state = state;
	}

	m_event.post();
	m_eventDelay.post();
}

AVFramePtr CVideoRender::waitAndGet(int ms)
{
	AVFramePtr frame = m_frames.pop();
	if (!frame)
	{
		if (m_event.timedwait(ms))
		{
			frame = m_frames.pop();
		}
	}
	return frame;
}

int CVideoRender::computeDelay(AVFramePtr& frame)
{
    int64_t pts = getPts(frame);
    TimePoint curPoint(getClock(), pts);

	TimePoint refPoint(m_originPoint);
	getRefPoint(refPoint);

	if (!refPoint.isSet() || refPoint.m_pts == AV_NOPTS_VALUE)
	{
		return frame->pkt_duration;
	}

	int delay = curPoint.getOffset(refPoint);
    delay -= 100 * 1000; /// 提前音频多少毫秒
    
    int lastDelay = 0;
    if (m_curPoint.isSet() && (m_curPoint.m_pts != AV_NOPTS_VALUE))
    {
        lastDelay = curPoint.getOffset(m_curPoint);
    }

    //CLog::debug("av sync. delay:%d, lastDelay:%d, delta:%d\n", delay/1000, lastDelay/1000, (delay-lastDelay)/1000);

    delay = (delay + lastDelay)/2;

    if (delay >= MAX_SYNC_TIME)
    {
        delay = lastDelay;
    }
    
	return delay;
}

void CVideoRender::delayTime(int microseconds)
{
    if (microseconds <= 0)
    {
        return;
    }

    m_eventDelay.timedwait(microseconds / 1000);
}

int64_t CVideoRender::getPts(AVFramePtr& frame)
{
    int64_t pts = av_frame_get_best_effort_timestamp(frame.get());

    return pts;
}

bool CVideoRender::getRefPoint(TimePoint& pt)
{
	if (m_refClock)
	{
        m_refClock->getTimePoint(pt.m_clock, pt.m_pts);
	}

    if (!pt.isSet())
	{
		pt.reset(m_originPoint);
	}
	return true;
}

void CVideoRender::handlePlay()
{
	m_originPoint.reset();
	m_curPoint.reset();
}

void CVideoRender::handlePause()
{
	m_originPoint.reset();
	m_curPoint.reset();
}

void CVideoRender::handleStop()
{
	m_originPoint.reset();
	m_curPoint.reset();

	m_scale = av_make_q(1,1);
}

void CVideoRender::handleStep()
{
	AVFramePtr frame = waitAndGet(-1);
	if (!frame)
	{
		return;
	}

    int64_t pts = getPts(frame);
    m_curPoint.reset(getClock(), pts);

	m_videoWindow->draw(frame.get());
}

void CVideoRender::handleNextKeyFrame()
{
	m_waitForKeyFrame = true;
}

void CVideoRender::handleSetScale(AVRational scale)
{
	m_scale = scale;

	m_originPoint.reset();
	m_curPoint.reset();
}

int64_t CVideoRender::getClock()
{
	int64_t clk = util::TimeHelper::getClock();
	if ((m_scale.num > 0) && (m_scale.num != m_scale.den))
	{
		clk = av_rescale(clk, m_scale.num, m_scale.den);
	}
	return clk;
}



} /* namespace av */

